library node;

import 'dart:collection';
import 'dart:html';
import 'package:logging/logging.dart';

import 'rbtree.dart';

void logPrint(LogRecord lrec) {
  print("${lrec.sequenceNumber} ${lrec.time} ${lrec.level} ${lrec.loggerName} ${lrec.message}");
}

final Logger logger = new Logger("node");


/**
 * Comparator for node features.
 */
int compareFeature(Object feature1, Object feature2) {
  if (feature1 is int) {
    final int i1 = feature1;

    if (feature2 is int) {
      final int i2 = feature2;
      return i1 - i2;

    } else if (feature2 is String) {
      return -1;

    } else {
      throw new ArgumentError("Invalid feature: ${feature2}");
    }

  } else if (feature1 is String) {
    final String s1 = feature1;

    if (feature2 is int) {
      final int i2 = feature2;
      return 1;

    } else if (feature2 is String) {
      final String s2 = feature2;
      return s1.compareTo(s2);

    } else {
      throw new ArgumentError("Invalid feature: ${feature2}");
    }

  } else {
    throw new ArgumentError("Invalid feature: ${feature1}");
  }
}

// -------------------------------------------------------------------------------------------------

class Node {
  /** Node parent. */
  Node parent;

  /** Node label. */
  String label;

  /** Sorted map of the node features. */
  final Map<Object, Node> features = new RedBlackTree.withOrder(compareFeature);

  /** HTML element reifying this node. */
  Element element = null;

  /**
   * Creates a Node.
   *
   * [parent] Parent Node, or null for a root node.
   * [label] Node label (text string).
   */
  Node(this.parent, this.label);

  /**
   * Creates a simple node.
   *
   * [label] Node label (text string).
   */
  Node.simple(String label) : this(null, label);

  /**
   * Creates a root node.
   */
  Node.root() : this(null, "root");

  /**
   * Adds a child node to this node.
   *
   * [feature] Name of the child node (string or int).
   * [child] Child node to add.
   * Returns this node, to chain statements.
   */
  Node addNamedChild(Object feature, Node child) {
    logger.fine("Adding feature named: ${feature}");
    child.parent = this;
    features[feature] = child;
    return this;
  }

  /**
   * Appends an ordered child to this node's tuple fragment.
   *
   * [child] Child node to append.
   */
  Node appendChild(Node child) {
    final int tupleSize = getTupleSize();
    logger.fine("Node.appendChild() : tuple size = ${tupleSize}");
    final int next = tupleSize + 1;
    features[next] = child;
    return this;
  }

  /**
   * Inserts a child node at a specified tuple [pos]ition.
   * Shifts existing children if necessary.
   *
   * [pos] Tuple position where to insert the child.
   * [child] Child node to insert.
   */
  Node tupleInsertChild(int pos, Node child) {
    logger.fine("Node.tupleInsertChild(pos=${pos}, child=${child})");
    Node shift = features[pos];
    while (child != null) {
      features[pos] = child;
      child = shift;
      pos += 1;
      shift = features[pos];
    }
    return this;
  }

  /**
   * Inserts a child node at a specified tuple [pos]ition.
   * Shifts existing children if necessary.
   *
   * [pos] Tuple position where to insert the child.
   * [child] Child node to insert.
   */
  Node tupleRemoveChild(int pos) {
    logger.fine("Node.tupleRemoveChild(pos=${pos})");
    Node shift = features[pos + 1];
    while (shift != null) {
      features[pos] = shift;
      pos += 1;
      shift = features[pos + 1];
    }
    features.remove(pos);
    return this;
  }

  /**
   * Returns the size of the tuple part of this node.
   */
  int getTupleSize() {
    int index = 1;
    for (final Object key in features.keys) {
      if (key != index) break;
      index += 1;
    }
    return index - 1;
  }

  /**
   * Whether this Node is a tuple.
   *
   * A tuple has features 1, 2, ..., N.
   */
  bool isTuple() {
    return getTupleSize() == features.length;
  }
}
